//! Monospaced bitmap fonts.
//!
//! This module contains support for drawing monospaced bitmap fonts and provides
//! several [built-in fonts].
//!
//! Additional custom fonts can be added by the application or other crates. This
//! is demonstrated in the `text-custom-font` example in the [examples repository].
//!
//! # Examples
//!
//! The [`text` module] contains examples how these fonts can be used in an application.
//!
//! # Built-in fonts
//!
//! Each built-in font is provided in different glyph subsets. The ASCII variant is the smallest
//! subset which saves memory in embedded applications, but only covers all characters of the English
//! language. The ISO 8859 subsets support a wide range of languages, see
//! [Wikipedia](https://en.wikipedia.org/wiki/ISO/IEC_8859#The_parts_of_ISO/IEC_8859) for a list of
//! languages.
//!
//! The table below shows the ASCII variant of the built-in fonts. See the [subset modules](#modules) for
//! an overview of the complete character set included in the other variants.
//!
// WARNING: The table between START-FONT-TABLE and END-FONT-TABLE is generated.
//          Use `just convert-fonts` to update the table.
//START-FONT-TABLE-ASCII
//! | Type | Screenshot | | Type | Screenshot |
//! |------|------------|-|------|------------|
//! | `FONT_4X6` | ![FONT_4X6]() | | `FONT_7X13_ITALIC` | ![FONT_7X13_ITALIC]() |
//! | `FONT_5X7` | ![FONT_5X7]() | | `FONT_7X14` | ![FONT_7X14]() |
//! | `FONT_5X8` | ![FONT_5X8]() | | `FONT_7X14_BOLD` | ![FONT_7X14_BOLD]() |
//! | `FONT_6X9` | ![FONT_6X9]() | | `FONT_8X13` | ![FONT_8X13]() |
//! | `FONT_6X10` | ![FONT_6X10]() | | `FONT_8X13_BOLD` | ![FONT_8X13_BOLD]() |
//! | `FONT_6X12` | ![FONT_6X12]() | | `FONT_8X13_ITALIC` | ![FONT_8X13_ITALIC]() |
//! | `FONT_6X13` | ![FONT_6X13]() | | `FONT_9X15` | ![FONT_9X15]() |
//! | `FONT_6X13_BOLD` | ![FONT_6X13_BOLD]() | | `FONT_9X15_BOLD` | ![FONT_9X15_BOLD]() |
//! | `FONT_6X13_ITALIC` | ![FONT_6X13_ITALIC]() | | `FONT_9X18` | ![FONT_9X18]() |
//! | `FONT_7X13` | ![FONT_7X13]() | | `FONT_9X18_BOLD` | ![FONT_9X18_BOLD]() |
//! | `FONT_7X13_BOLD` | ![FONT_7X13_BOLD]() | | `FONT_10X20` | ![FONT_10X20]() |
//END-FONT-TABLE
//!
//! [built-in fonts]: #built-in-fonts
//! [`text` module]: super::text#examples
//! [examples repository]:  https://github.com/embedded-graphics/examples

mod draw_target;
mod generated;
pub mod mapping;
mod mono_text_style;

use core::fmt;

pub use generated::*;
pub use mono_text_style::{MonoTextStyle, MonoTextStyleBuilder};

use crate::{
    geometry::{OriginDimensions, Point, Size},
    image::{ImageRaw, SubImage},
    mono_font::mapping::GlyphMapping,
    pixelcolor::BinaryColor,
    primitives::Rectangle,
};

/// Monospaced bitmap font.
///
/// See the [module documentation] for more information about using fonts.
///
/// [module documentation]: self
#[derive(Clone, Copy)]
pub struct MonoFont<'a> {
    /// Raw image data containing the font.
    pub image: ImageRaw<'a, BinaryColor>,

    /// Size of a single character in pixel.
    pub character_size: Size,

    /// Spacing between characters.
    ///
    /// The spacing defines how many empty pixels are added horizontally between adjacent characters
    /// on a single line of text.
    pub character_spacing: u32,

    /// The baseline.
    ///
    /// Offset from the top of the glyph bounding box to the baseline.
    pub baseline: u32,

    /// Strikethrough decoration dimensions.
    pub strikethrough: DecorationDimensions,

    /// Underline decoration dimensions.
    pub underline: DecorationDimensions,

    /// Glyph mapping.
    pub glyph_mapping: &'a dyn GlyphMapping,
}

impl MonoFont<'_> {
    /// Returns a subimage for a glyph.
    pub(crate) fn glyph(&self, c: char) -> SubImage<'_, ImageRaw<BinaryColor>> {
        if self.character_size.width == 0 || self.image.size().width < self.character_size.width {
            return SubImage::new_unchecked(&self.image, Rectangle::zero());
        }

        let glyphs_per_row = self.image.size().width / self.character_size.width;

        // Char _code_ offset from first char, most often a space
        // E.g. first char = ' ' (32), target char = '!' (33), offset = 33 - 32 = 1
        let glyph_index = self.glyph_mapping.index(c) as u32;
        let row = glyph_index / glyphs_per_row;

        // Top left corner of character, in pixels
        let char_x = (glyph_index - (row * glyphs_per_row)) * self.character_size.width;
        let char_y = row * self.character_size.height;

        SubImage::new_unchecked(
            &self.image,
            Rectangle::new(
                Point::new(char_x as i32, char_y as i32),
                self.character_size,
            ),
        )
    }
}

impl PartialEq for MonoFont<'_> {
    #[allow(trivial_casts)]
    fn eq(&self, other: &Self) -> bool {
        self.image == other.image
            && self.character_size == other.character_size
            && self.character_spacing == other.character_spacing
            && self.baseline == other.baseline
            && self.strikethrough == other.strikethrough
            && self.underline == other.underline
            && core::ptr::eq(
                self.glyph_mapping as *const dyn GlyphMapping as *const u8,
                other.glyph_mapping as *const dyn GlyphMapping as *const u8,
            )
    }
}

impl fmt::Debug for MonoFont<'_> {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        f.debug_struct("MonoFont")
            .field("image", &self.image)
            .field("character_size", &self.character_size)
            .field("character_spacing", &self.character_spacing)
            .field("baseline", &self.baseline)
            .field("strikethrough", &self.strikethrough)
            .field("underline", &self.underline)
            .field("glyph_mapping", &"?")
            .finish_non_exhaustive()
    }
}

#[cfg(feature = "defmt")]
impl ::defmt::Format for MonoFont<'_> {
    fn format(&self, f: ::defmt::Formatter) {
        ::defmt::write!(
            f,
            "MonoFont {{ image: {}, character_size: {}, character_spacing: {}, baseline: {}, strikethrough: {}, underline: {}, .. }}",
            &self.image,
            &self.character_size,
            &self.character_spacing,
            &self.baseline,
            &self.strikethrough,
            &self.underline,

        )
    }
}

/// Decoration dimensions.
///
/// `DecorationDimensions` is used to specify the position and height of underline and strikethrough
/// decorations in [`MonoFont`]s.
///
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Default)]
#[cfg_attr(feature = "defmt", derive(::defmt::Format))]
pub struct DecorationDimensions {
    /// Offset from the top of the character to the top of the decoration.
    pub offset: u32,
    /// Height of the decoration.
    pub height: u32,
}

impl DecorationDimensions {
    /// Creates new decoration dimensions.
    pub const fn new(offset: u32, height: u32) -> Self {
        Self { offset, height }
    }

    /// Creates a new default strikethrough decoration for the given glyph height.
    pub const fn default_strikethrough(glyph_height: u32) -> Self {
        Self {
            offset: glyph_height.saturating_sub(1) / 2,
            height: 1,
        }
    }

    /// Creates a new default underline decoration for the given glyph height.
    pub const fn default_underline(glyph_height: u32) -> Self {
        Self {
            offset: glyph_height + 1,
            height: 1,
        }
    }

    fn to_rectangle(&self, position: Point, width: u32) -> Rectangle {
        let top_left = position + Size::new(0, self.offset);
        let size = Size::new(width, self.height);

        Rectangle::new(top_left, size)
    }
}

const NULL_FONT: MonoFont = MonoFont {
    image: ImageRaw::new(&[], 1),
    character_size: Size::zero(),
    character_spacing: 0,
    baseline: 0,
    strikethrough: DecorationDimensions::new(0, 0),
    underline: DecorationDimensions::new(0, 0),
    glyph_mapping: &mapping::ASCII,
};

#[cfg(test)]
pub(crate) mod tests {
    use arrayvec::ArrayString;

    use super::*;
    use crate::{
        framebuffer::{buffer_size, Framebuffer},
        geometry::{Dimensions, Point},
        image::{GetPixel, Image},
        mock_display::MockDisplay,
        mono_font::{mapping::Mapping, MonoTextStyleBuilder},
        pixelcolor::{
            raw::{LittleEndian, RawU1},
            BinaryColor,
        },
        text::{Baseline, Text},
        Drawable,
    };

    /// Draws a text using the given font and checks it against the expected pattern.
    #[track_caller]
    pub fn assert_text_from_pattern(text: &str, font: &MonoFont, pattern: &[&str]) {
        let style = MonoTextStyleBuilder::new()
            .font(font)
            .text_color(BinaryColor::On)
            .build();

        let mut display = MockDisplay::new();
        Text::with_baseline(text, Point::zero(), style, Baseline::Top)
            .draw(&mut display)
            .unwrap();

        display.assert_pattern(pattern);
    }

    /// Test if the baseline constant is set correctly.
    ///
    /// This test assumes that the character `A` is on the baseline.
    pub fn test_baseline(font: &MonoFont) {
        let style = MonoTextStyleBuilder::new()
            .font(font)
            .text_color(BinaryColor::On)
            .build();

        // Draw 'A' character to determine it's baseline
        let mut display = MockDisplay::new();
        Text::with_baseline("A", Point::zero(), style, Baseline::Top)
            .draw(&mut display)
            .unwrap();

        let baseline = display.affected_area().bottom_right().unwrap().y as u32;

        assert_eq!(font.baseline, baseline);
    }

    #[test]
    fn baseline() {
        test_baseline(&ascii::FONT_4X6);
        test_baseline(&ascii::FONT_5X7);
        test_baseline(&ascii::FONT_5X8);
        test_baseline(&ascii::FONT_6X10);
        test_baseline(&ascii::FONT_6X12);
        test_baseline(&ascii::FONT_6X13_BOLD);
        test_baseline(&ascii::FONT_6X13);
        test_baseline(&ascii::FONT_6X13_ITALIC);
        test_baseline(&ascii::FONT_6X9);
        test_baseline(&ascii::FONT_7X13_BOLD);
        test_baseline(&ascii::FONT_7X13);
        test_baseline(&ascii::FONT_7X13_ITALIC);
        test_baseline(&ascii::FONT_7X14_BOLD);
        test_baseline(&ascii::FONT_7X14);
        test_baseline(&ascii::FONT_8X13_BOLD);
        test_baseline(&ascii::FONT_8X13);
        test_baseline(&ascii::FONT_8X13_ITALIC);
        test_baseline(&ascii::FONT_9X15_BOLD);
        test_baseline(&ascii::FONT_9X15);
        test_baseline(&ascii::FONT_9X18_BOLD);
        test_baseline(&ascii::FONT_9X18);
        test_baseline(&ascii::FONT_10X20);
    }

    /// (Statically) test that [`MonoFont: Send + Sync`].
    fn _mono_font_is_sync()
    where
        for<'a> MonoFont<'a>: Send + Sync,
    {
    }

    fn new_framebuffer() -> Framebuffer<
        BinaryColor,
        RawU1,
        LittleEndian,
        96,
        200,
        { buffer_size::<BinaryColor>(96, 200) },
    > {
        Framebuffer::new()
    }

    fn dump_framebuffer<T: GetPixel<Color = BinaryColor> + Dimensions, const N: usize>(
        framebuffer: &T,
        output: &mut ArrayString<N>,
    ) {
        let bb = framebuffer.bounding_box();

        for y in bb.rows() {
            for x in bb.columns() {
                let c = match framebuffer.pixel(Point::new(x, y)).unwrap() {
                    BinaryColor::Off => ' ',
                    BinaryColor::On => '#',
                };
                output.push(c);
            }
            output.push('\n');
        }
    }

    #[test]
    fn draw_font_subsets() {
        let fonts = &[
            (Mapping::Ascii, ascii::FONT_6X13),
            (Mapping::Iso8859_1, iso_8859_1::FONT_6X13),
            (Mapping::Iso8859_10, iso_8859_10::FONT_6X13),
            (Mapping::Iso8859_13, iso_8859_13::FONT_6X13),
            (Mapping::Iso8859_14, iso_8859_14::FONT_6X13),
            (Mapping::Iso8859_15, iso_8859_15::FONT_6X13),
            (Mapping::Iso8859_16, iso_8859_16::FONT_6X13),
            (Mapping::Iso8859_2, iso_8859_2::FONT_6X13),
            (Mapping::Iso8859_3, iso_8859_3::FONT_6X13),
            (Mapping::Iso8859_4, iso_8859_4::FONT_6X13),
            (Mapping::Iso8859_5, iso_8859_5::FONT_6X13),
            (Mapping::Iso8859_7, iso_8859_7::FONT_6X13),
            (Mapping::Iso8859_9, iso_8859_9::FONT_6X13),
            (Mapping::JisX0201, jis_x0201::FONT_6X13),
        ];

        for (mapping, font) in fonts {
            let mut expected = new_framebuffer();
            Image::new(&font.image, Point::zero())
                .draw(&mut expected)
                .unwrap();

            let chars_per_row = (font.image.size().width / font.character_size.width) as usize;

            let mut text = ArrayString::<1024>::new();
            for (i, c) in mapping.glyph_mapping().chars().enumerate() {
                if i % chars_per_row == 0 && i != 0 {
                    text.push('\n');
                }
                text.push(c);
            }

            let mut output = new_framebuffer();
            Text::with_baseline(
                &text,
                Point::zero(),
                MonoTextStyle::new(&font, BinaryColor::On),
                Baseline::Top,
            )
            .draw(&mut output)
            .unwrap();

            if expected != output {
                let mut message = ArrayString::<65536>::new();
                message.push_str("Output:\n");
                dump_framebuffer(&output, &mut message);
                message.push_str("\nExpected:\n");
                dump_framebuffer(&expected, &mut message);

                panic!("{}", message)
            }
        }
    }

    #[test]
    fn zero_width_image() {
        const ZERO_WIDTH: MonoFont = MonoFont {
            image: ImageRaw::new(&[], 0),
            character_size: Size::zero(),
            character_spacing: 0,
            baseline: 0,
            strikethrough: DecorationDimensions::new(0, 0),
            underline: DecorationDimensions::new(0, 0),
            glyph_mapping: &mapping::ASCII,
        };

        let mut display = MockDisplay::new();
        Text::new(
            " ",
            Point::new_equal(20),
            MonoTextStyle::new(&ZERO_WIDTH, BinaryColor::On),
        )
        .draw(&mut display)
        .unwrap();

        display.assert_pattern(&[]);
    }

    #[test]
    fn image_width_less_than_character_width() {
        const NOT_WIDE_ENOUGH: MonoFont = MonoFont {
            image: ImageRaw::new(&[0xAA, 0xAA], 4),
            character_size: Size::new(5, 2),
            character_spacing: 0,
            baseline: 0,
            strikethrough: DecorationDimensions::new(0, 0),
            underline: DecorationDimensions::new(0, 0),
            glyph_mapping: &mapping::ASCII,
        };

        let mut display = MockDisplay::new();
        Text::new(
            " ",
            Point::new_equal(20),
            MonoTextStyle::new(&NOT_WIDE_ENOUGH, BinaryColor::On),
        )
        .draw(&mut display)
        .unwrap();

        display.assert_pattern(&[]);
    }

    #[test]
    fn image_height_less_than_character_height() {
        const NOT_HIGH_ENOUGH: MonoFont = MonoFont {
            image: ImageRaw::new(&[0xAA, 0xAA], 4),
            character_size: Size::new(4, 1),
            character_spacing: 0,
            baseline: 0,
            strikethrough: DecorationDimensions::new(0, 0),
            underline: DecorationDimensions::new(0, 0),
            glyph_mapping: &mapping::ASCII,
        };

        let mut display = MockDisplay::new();
        Text::new(
            " ",
            Point::zero(),
            MonoTextStyle::new(&NOT_HIGH_ENOUGH, BinaryColor::On),
        )
        .draw(&mut display)
        .unwrap();

        display.assert_pattern(&["# #"]);
    }
}
